﻿using System;
using Tao.OpenGl;
using Tao.FreeGlut;
using System.Threading;
using MoveModel;
using MoveView.MyMath;
using MoveView.Model;

namespace MoveView
{

    class StereoRender
    {
        //flags
        private int stereo = 0;
        private bool fullscreen = true;

        private CAMERA camera;

        private int windowWidth = 1920;
        private int windowHeight = 1080;
        private int windowId = 0;

        private Move move;
        private Drawing drawing;
        private ModelViewCalc mvc;
        private int calibrationStep = 1;

        public StereoRender(int isStereo, bool isFullscreen, int width, int height, Move move)
        {
            this.move = move;
            drawing = new Drawing();

            //temp
            drawing.StrokeHistory.Add(new StrokeDisplay());

            camera.screenheight = height;
            camera.screenwidth = width;

            stereo = isStereo;
            fullscreen = isFullscreen;

            Glut.glutInit();
            Glut.glutInitDisplayMode(Glut.GLUT_DOUBLE | Glut.GLUT_RGB | Glut.GLUT_DEPTH);
            windowId = Glut.glutCreateWindow("Move Sculpting");
            Glut.glutReshapeWindow(windowWidth, windowHeight);
            if (fullscreen)
                Glut.glutFullScreen();

            Glut.glutDisplayFunc(Display);
            Glut.glutVisibilityFunc(HandleVisibility);
            Glut.glutReshapeFunc(HandleReshape);
            Glut.glutSetCursor(Glut.GLUT_CURSOR_NONE);
            //Glut.glutSpecialFunc(HandleSpecialKeys);
            //Glut.glutKeyboardFunc(HandleKeyboard);

            Gl.glShadeModel(Gl.GL_SMOOTH);
            Gl.glClearColor(0, 0, 0, 1);

            CreateEvironment();
            CameraHome();

            Glut.glutMainLoop();
        }

        private void HandleSpecialKeys(int key, int x, int y)
        {
            switch (key)
            {
                case Glut.GLUT_KEY_F1:
                    Stroke.PolygonMode = !Stroke.PolygonMode;
                    break;
                default:
                    break;
            }
        }

        private void HandleKeyboard(byte key, int x, int y)
        {
            switch (key)
            {
                case (int)'f':
                    fullscreen = !fullscreen;
                    Glut.glutFullScreen();
                    break;
                case (int)'s':
                    stereo = (++stereo) % 3;
                    break;
                case 27: //ESC
                    Glut.glutDestroyWindow(windowId);
                    break;
                default:
                    break;
            }
        }

        private void CreateEvironment()
        {
            Gl.glEnable(Gl.GL_DEPTH_TEST);
            Gl.glDisable(Gl.GL_LINE_SMOOTH);
            Gl.glDisable(Gl.GL_POINT_SMOOTH);
            Gl.glEnable(Gl.GL_POLYGON_SMOOTH);
            Gl.glShadeModel(Gl.GL_SMOOTH);
            Gl.glDisable(Gl.GL_DITHER);
            Gl.glDisable(Gl.GL_CULL_FACE);

            Gl.glLineWidth(1.0f);
            Gl.glPointSize(1.0f);

            Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_FILL);
            Gl.glFrontFace(Gl.GL_CW);
            Gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            Gl.glColorMaterial(Gl.GL_FRONT_AND_BACK, Gl.GL_AMBIENT_AND_DIFFUSE);
            Gl.glEnable(Gl.GL_COLOR_MATERIAL);
            Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT, 1);
        }

        private void Display()
        {

            XYZ r = new XYZ();
            double ratio, radians, wd2, ndfl;
            double left, right, top, bottom, near = 0.1, far = 10000;

            //Clip to avoid extreme stereo
            if (stereo > 0)
                near = camera.focallength / 5;

            //Misc stuff
            ratio = camera.screenwidth / (double)camera.screenheight;
            radians = Constants.DTOR * camera.aperture / 2;
            wd2 = near * Math.Tan(radians);
            ndfl = near / camera.focallength;

            //Clear buffers
            Gl.glDrawBuffer(Gl.GL_BACK_LEFT);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);


            if (stereo > 0)
            {
                Gl.glDrawBuffer(Gl.GL_BACK_RIGHT);
                Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

                if (stereo == 2)
                    Gl.glColorMask(false, true, true, true);

                //Derive two eyes positions
                r = Constants.Crossproduct(camera.vd, camera.vu);
                Constants.Normalize(ref r);
                r.x *= camera.eyesep / 2.0;
                r.y *= camera.eyesep / 2.0;
                r.z *= camera.eyesep / 2.0;


                //Left eye
                if (stereo == 1)
                    Gl.glViewport(0, 0, windowWidth / 2, windowHeight);
                else
                    Gl.glViewport(0, 0, windowWidth, windowHeight);

                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glLoadIdentity();
                left = -ratio * wd2 - 0.5 * camera.eyesep * ndfl;
                right = ratio * wd2 - 0.5 * camera.eyesep * ndfl;
                top = wd2;
                bottom = -wd2;
                Gl.glFrustum(left, right, bottom, top, near, far);

                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glDrawBuffer(Gl.GL_BACK_RIGHT);
                Gl.glLoadIdentity();

                MakeCursor();
                Gl.glPushMatrix();
                Glu.gluLookAt(
                        camera.cc.x + r.x, camera.cc.y + r.y, camera.cc.z + r.z,
                        camera.cc.x + r.x + camera.vd.x,
                        camera.cc.y + r.y + camera.vd.y,
                        camera.cc.z + r.z + camera.vd.z,
                        camera.vu.x, camera.vu.y, camera.vu.z
                    );           
                Gl.glPopMatrix();

                MakeLightning(true);
                if (calibrationStep > 0)
                    calibrationDisplay();
                else
                    MakeGeometry();
                
                //right Eye
                if (stereo == 2)
                {
                    Gl.glColorMask(true, false, false, true); 
                    Gl.glDrawBuffer(Gl.GL_BACK_RIGHT);
                    Gl.glClear(Gl.GL_DEPTH_BUFFER_BIT);                   
                }
                else
                    Gl.glViewport(windowWidth / 2, 0, windowWidth / 2, windowHeight);
                
                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glLoadIdentity();
                left = -ratio * wd2 + 0.5 * camera.eyesep * ndfl;
                right = ratio * wd2 + 0.5 * camera.eyesep * ndfl;
                top = wd2;
                bottom = -wd2;
                Gl.glFrustum(left, right, bottom, top, near, far);

                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glDrawBuffer(Gl.GL_BACK_LEFT);
                Gl.glLoadIdentity();


                MakeCursor();

                Gl.glPushMatrix();
                Glu.gluLookAt(
                    camera.cc.x - r.x, camera.cc.y - r.y, camera.cc.z - r.z,
                    camera.cc.x - r.x + camera.vd.x,
                    camera.cc.y - r.y + camera.vd.y,
                    camera.cc.z - r.z + camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                );
                Gl.glPopMatrix();

                MakeLightning(true);
                if (calibrationStep > 0)
                    calibrationDisplay();
                else
                    MakeGeometry();

                if (stereo == 2)
                    Gl.glColorMask(true, true, true, true);
            }
            else
            {
                //Two dimensional

                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glLoadIdentity();
                left = -ratio * wd2;
                right = ratio * wd2;
                top = wd2;
                bottom = -wd2;
                Gl.glFrustum(left, right, bottom, top, near, far);

                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glDrawBuffer(Gl.GL_BACK_LEFT);
                Gl.glLoadIdentity();

                Glu.gluLookAt(
                    0, 0, camera.focallength,
                    camera.vd.x,
                    camera.vd.y,
                    camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                );

                MakeCursor();

                Gl.glPushMatrix();
                Glu.gluLookAt(
                    camera.cc.x, camera.cc.y, camera.cc.z,
                    camera.vd.x,
                    camera.vd.y,
                    camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                );
                Gl.glPopMatrix();

                MakeLightning(true);
                if (calibrationStep > 0)
                    calibrationDisplay();
                else
                    MakeGeometry();
                
            }

            //Gl.glFlush(); //Not not neccesary for double buffers
            Glut.glutSwapBuffers();

            if (calibrationStep > 0)
                HandleMoveInputCalibration();
            else
                HandleMoveInput();
        }

        bool rightEye = false;
        void calibrationDisplay()
        {
            MakeLightning(false);
            Gl.glPushMatrix();
            Gl.glColor3d(1.0, 1.0, 1.0);
            if (stereo > 0)
            {
                //TODO: Change display method since, this will display in normal width
                if(rightEye)
                    Gl.glRasterPos2d(-90, 10);
                else
                    Gl.glRasterPos2d(-1000, 10);
                rightEye = !rightEye;
            }
            else
                Gl.glRasterPos2d(-55, 30);
            switch (calibrationStep)
            {
                case 1:
                    Glut.glutBitmapString(Glut.GLUT_BITMAP_TIMES_ROMAN_24, "Calibration in progress. To cancel, press circle button.\nHold controller in front of you with your maximum comfortable reach and press Move button to continue.");
                    break;
                case 2:
                    Glut.glutBitmapString(Glut.GLUT_BITMAP_TIMES_ROMAN_24, "Calibration in progress. To cancel, press circle button.\nHold controller in front of you with your minimun comfortable reach and press Move button to continue.");
                    break;
                case 3:
                    Glut.glutBitmapString(Glut.GLUT_BITMAP_TIMES_ROMAN_24, "Calibration process complete. Thank you for cooperation.");
                    calibrationStep++;
                    break;
                case 4:
                    Thread.Sleep(1500);
                    move.calibrationDone = true;
                    calibrationStep = 0;
                    break;
                default:
                    break;
            }
            Gl.glPopMatrix();
            MakeLightning(true);
        }

        private void MakeCursor()
        {
            if (!holdBrush)
            {
                Gl.glPushMatrix();
                Gl.glTranslated(move.X, move.Y, move.Radius);
                if (move.Trigger > 0)
                    drawing.drawWireShape(move.Trigger / 20);
                else
                    drawing.drawWireShape(2);
                Gl.glPopMatrix();
            }
        }

        private bool holdBrush = false;
        private void MakeGeometry()
        {
            Glu.gluLookAt(camera.cc.x, camera.cc.y, camera.cc.z, 0, 0, 0, 0, 1, 0);
            mvc = new ModelViewCalc(camera.cc.x, camera.cc.y, camera.cc.z, 0, 0, 0, 0, 1, 0);
            
            //Glut.glutWireCube(20);
            DrawHistory();

            //linie pomocnicze
            Gl.glColor3d(1, 1, 1);
            Gl.glLineWidth(1);
            Gl.glBegin(Gl.GL_LINES);
            Gl.glVertex3d(0, -500, 0);
            Gl.glVertex3d(0, 500, 0);
            Gl.glVertex3d(0, 0, -500);
            Gl.glVertex3d(0, 0, 500);
            Gl.glVertex3d(-500, 0, 0);
            Gl.glVertex3d(500, 0, 0);
            Gl.glEnd();

        }

        private void DrawHistory()
        {
            foreach (StrokeDisplay stroke in drawing.StrokeHistory)
            {
                stroke.DrawStroke();
            }
        }

        private void DrawOptimization()
        {
            var rgCord = mvc.CalcGlobalCoordinates(-move.X, move.Y, -move.Radius - camera.focallength);

            //Add first stroke
            if (drawing.StrokeHistory.Count == 0)
                drawing.StrokeHistory.Add(new StrokeDisplay(drawing.ShapeNumber));

            drawing.StrokeHistory[drawing.StrokeHistory.Count - 1].AddShape(
                        new Shape(
                        //-move.X, move.Y, -move.Radius - camera.focallength,
                        rgCord[0], rgCord[1], rgCord[2],
                        drawing.ShapeNumber,
                        move.Trigger / 20.0,
                        drawing.ColorNumber));
        }

        private double prevX = 0, prevY = 0;
        private bool wasReleased = false;


        private void HandleMoveInputCalibration()
        {
            if (move.IsCircleBtn)
            {
                calibrationStep = 0;
                return;
            }
            if (move.IsMoveButton)
            {
                if (calibrationStep == 1)
                    move.setCalibrationMin();
                if (calibrationStep == 2)
                    move.setCalibrationMax();
                wasReleased = true;
            }
            else if (!move.IsMoveButton && wasReleased)
            {
                calibrationStep++;
                wasReleased = false;
            }

        }

        private void HandleMoveInput()
        {
            if (move.IsMoveButton)
            {
                if (Math.Abs(move.X - prevX) > 5)
                    camera.cc.a += (move.X - prevX) / 100;
                if (Math.Abs(move.Y - prevY) > 5)
                    camera.cc.p += (move.Y - prevY) / 100;
                holdBrush = true;
            }
            else
                holdBrush = false;
            if (move.Trigger > 0)
            {
                drawing.UndoHistory.Clear();
                DrawOptimization();
                wasReleased = true;
            }
            else
                if (wasReleased)
                {
                    drawing.StrokeHistory.Add(new StrokeDisplay(0, 0));
                    wasReleased = false;
                }
            if (move.IsSquareBtn)
                drawing.ColorNumber++;
            if (move.IsCrossBtn)
                drawing.ShapeNumber++;
            if (move.IsCircleBtn)
                UndoStroke();
            if (move.IsTriangleBtn)
                RedoStroke();
            if (move.IsSelectBtn)
                CameraHome();
            if (move.IsStartBtn)
                drawing.StrokeHistory.Clear();
            if (move.IsPSButton)
                Glut.glutDestroyWindow(windowId);
            prevX = move.X;
            prevY = move.Y;
        }

        private void UndoStroke()
        {
            if (drawing.StrokeHistory.Count < 2) return;
            var undoneElement = drawing.StrokeHistory[drawing.StrokeHistory.Count - 2];
            drawing.UndoHistory.Add(undoneElement);
            drawing.StrokeHistory.Remove(undoneElement);
        }

        private void RedoStroke()
        {
            if (drawing.UndoHistory.Count == 0) return;
            var redoneElement = drawing.UndoHistory[drawing.UndoHistory.Count - 1];
            drawing.StrokeHistory.RemoveAt(drawing.StrokeHistory.Count - 1);
            drawing.StrokeHistory.Add(redoneElement);
            drawing.StrokeHistory.Add(new StrokeDisplay(0, 0));
            drawing.UndoHistory.Remove(redoneElement);
        }


        private void MakeLightning(bool isOn)
        {
            if (isOn)
            {
                Gl.glEnable(Gl.GL_LIGHTING);
                Gl.glEnable(Gl.GL_LIGHT0);
                float[] light_pos0 = { 0f, 1f, 0f, 0f };
                Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, light_pos0);

                Gl.glEnable(Gl.GL_LIGHT1);
                float[] light_pos2 = { 0f, -1f, 0f, 0f };
                Gl.glLightfv(Gl.GL_LIGHT1, Gl.GL_POSITION, light_pos2);
            }
            else
                Gl.glDisable(Gl.GL_LIGHTING);
        }

        private void HandleReshape(int w, int h)
        {
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
            Gl.glViewport(0, 0, w, h);
            camera.screenheight = h;
            camera.screenwidth = w;
            windowHeight = h;
            windowWidth = w;
        }

        private void HandleIdle()
        {
            Glut.glutPostRedisplay();
            Thread.Sleep(25);
        }

        private void HandleVisibility(int visible)
        {
            if (visible == Glut.GLUT_VISIBLE)
                Glut.glutIdleFunc(HandleIdle);
            else
                Glut.glutIdleFunc(null);
        }
        private void CameraHome()
        {
            camera.aperture = 50;
            camera.focallength = 70;
            camera.eyesep = camera.focallength / 20;

            camera.vd.x = -1;
            camera.vd.y = 0;
            camera.vd.z = 0;

            camera.vu.x = 0;
            camera.vu.y = 1;
            camera.vu.z = 0;

            camera.cc = new CameraCalc(camera.focallength);
        }
    }
}
